package com.leon.androidcoursedesign.utils;

import android.util.Log;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Stack;

public class CalculateUtils {
    /**
     * 获取算式结果
     *
     * @param formula   算式
     * @return  返回算试结果
     */
    public String calFormula(String formula) {
        String result;

        try {
            List<String> infixExpressionList = toInfixExpressionList(formula);
            List<String> niBoLanList = toNiBoLan(infixExpressionList);

            result = String.valueOf(calResult(niBoLanList));
        } catch (Exception e) {
            e.printStackTrace();
            result = "该算式计算失败，请检查算式内容";
        }

        return result;
    }
    
    /**
     * 将string类型的中缀表达式转换为list形式的中缀表达式
     *
     * @param formula   算式
     * @return  返回list形式的中缀表达式
     */
    private List<String> toInfixExpressionList (String formula) {
        List<String> numbers = getNumbers(formula);

        String s = formula.replaceAll("[\\d.]", "|").replaceAll("[|]+", "|");
        List<String> formulaList = Arrays.asList(s.split(""));

        int count = 0;
        for (String s1 : formulaList) {
            if (s1.equals("|")) count++;
        }

        int i = 0;
        int y = 0;
        while (y != count) {
            if (formulaList.get(i).equals("|")) {
                formulaList.set(i, numbers.get(y));
                y++;
            }
            i++;
        }

        return formulaList;
    }

    /**
     * 获取formula的所有number
     *
     * @param formula   formula
     * @return  返回formula中的所有number(Double)
     */
    private List<String> getNumbers(String formula) {
        String[] tempArray = formula.split("[()×÷+\\-]");
        List<String> numbers = new ArrayList<>();
        for (String s : tempArray) {
            if (!s.equals("")) {
                numbers.add(s);
            }
        }

        return numbers;
    }

    /**
     * 中缀表达式转逆波兰表达式
     *
     * @param list  list形式的中缀表达式
     * @return  返回list形式的逆波兰表达式
     */
    private List<String> toNiBoLan(List<String> list) {
        Stack<String> symbolStack = new Stack<>();
        List<String> niBoLanList = new ArrayList<>();

        for (int i = 0; i < list.size(); i++) {
            String temp1 = list.get(i);
            String temp2;

            if (temp1.matches("\\d") || temp1.matches("\\d+.\\d+")) {
                niBoLanList.add(temp1);
            } else if (temp1.equals("(")) {
                symbolStack.push(temp1);
            } else if (temp1.equals(")")) {
                String innerTemp;
                while (!(innerTemp = symbolStack.pop()).equals("(")) {
                    niBoLanList.add(innerTemp);
                }
            } else {
                // 如果是操作符，且操作符比栈顶元素小，将栈顶元素出栈输出，直到操作符大于等于栈顶
                while (!symbolStack.empty() && getPriority((temp2 = symbolStack.peek())) > getPriority(temp1)) {
                    niBoLanList.add(temp2);
                    symbolStack.pop();
                }
                // 将操作符压入至栈内
                symbolStack.push(temp1);
            }
        }

        // 将所有操作符从栈中弹出
        while (!symbolStack.empty()) {
            niBoLanList.add(symbolStack.pop());
        }

        return niBoLanList;
    }

    /**
     * 获取运算符优先级
     *
     * @param operator    运算符
     * @return  返回该运算符级别
     */
    private int getPriority(String operator) {
        switch(operator){
            case "+":
            case "-":
                return 1;
            case "×":
            case "÷":
                return 2;
            default:
                return 0;
        }
    }

    /**
     * 计算逆波兰表达式结果
     *
     * @param list  list形式的逆波兰表达式
     * @return  算式结果
     */
    private double calResult(List<String> list) {
        // 用于保留小数点前三位使用
        DecimalFormat decimalFormat = new DecimalFormat("#.###");
        Stack<Double> doubleStack = new Stack<>();
        Log.d("Leon", "calResult: " + list.toString());
        for (String s : list) {
            if (s.matches("\\d+") || s.matches("\\d+.\\d+")) {
                doubleStack.push(Double.parseDouble(s));
            } else {
                Double value1 = doubleStack.pop();
                Double value2 = doubleStack.pop();

                double result;
                switch (s) {
                    case "+":
                        result = value1 + value2;
                        break;
                    case "-":
                        result = value1 > value2 ? value1 - value2 : value2 - value1;
//                        result = value2 - value1;
                        break;
                    case "×":
                        result = value1 * value2;
                        break;
                    case "÷":
                        result = value2 / value1;
                        break;
                    default:
                        result = 0.0;
                }

                doubleStack.push(Double.parseDouble(decimalFormat.format(result)));
            }
        }

        return doubleStack.pop();
    }
}
